/*
 * anima/init.cc - Initialization functions.
 * Copyright (C) 2007  Mikhail Vorozhtsov
 * See the LICENSE section of the README file for details.
 */

/* $Id$ */

#include HEADER (anima,init.hh)
#include HEADER (anima,cmdline.hh)
#include HEADER (anima,console.hh)
#include HEADER (anima,pdb.hh)
#include HEADER (anima,mm.hh)
#include HEADER (anima,cpu.hh)
#include HEADER (anima,space.hh)
#include HEADER (anima,thread.hh)
#include HEADER (anima,locks.hh)
#include HEADER (elbp,elbp.hh)
#include HEADER (elf,elf.hh)
#include HEADER (utils,memory.hh)

START_NAMESPACE (anima)

extern "C" void (* ctors_start[]) ();
extern "C" void (* ctors_end[]) ();

static spin_lock64_t init_lock;

bool init::_done_p;
space_ref_t init::main_space;

static void
init_cpu (bool bsp_p UNUSED)
{
  cpu_ref_t cpu_ref = cpu_t::get (cpu_t::get_current_id ());

  if (cpu_ref.null_p ())
    return;

  cpu_t *cpu = cpu_ref.get ();

  cpu->attach ();

  init_lock.unlock ();

  cpu_t::enable_ints ();

  while (true)
    cpu_t::wait_for_int ();
}

extern "C" void
init_bsp (u64_t elbp_magic, elbp::info_t *elbp_info)
{
  // Call global constructors.
  for (u64_t i = 0; &ctors_start[i] < ctors_end; ++i)
    ctors_start[i] ();

  // Check ELBP magic.
  if (elbp_magic != elbp::info_t::MAGIC)
    {
      console::init ();
      console::fatal ("Invalid ELBP magic %P (%P expected).",
                      elbp_magic, elbp::info_t::MAGIC);
      return;
    }

  // Try to parse command line.
  cmdline::parse (elbp_info->cmdline, false);

  // Greet user.
  console::init ();
  console::print ("This is Anima microkernel.\n");

  // Report about possible errors in command line.
  if (!cmdline::parse (elbp_info->cmdline, true))
    {
      console::fatal ("Command line parsing failed.");
      return;
    }

  u64_t total_cmdlines_len = 0;
  u64_t total_mods_size = 0;
  u64_t mods_count = 0;

  // Sanity check on passed module infos.
  for (const elbp::mod_info_t *mod_info = elbp_info->mod_info;
       mod_info; mod_info = mod_info->next)
    {
      if (mod_info->addr != CUT (mod_info->addr, mm::PAGE_SIZE))
        {
          console::fatal ("Module is not page-aligned.");
          return;
        }

      mods_count += 1;
      total_cmdlines_len = utils::length (mod_info->cmdline) + 1;
      total_mods_size += ALIGN (mod_info->size, mm::PAGE_SIZE);

      if (mod_info->size == 0)
        continue;

      if (-mod_info->size <= mod_info->addr)
        {
          console::fatal ("Module size overflow.");
          return;
        }

      for (const elbp::mod_info_t *mod_info1 = elbp_info->mod_info;
           mod_info1 != mod_info; mod_info1 = mod_info1->next)
        if (utils::in_range_p (mod_info->addr, mod_info1->addr,
                               mod_info1->addr + mod_info1->size)
            || utils::in_range_p (mod_info1->addr, mod_info->addr,
                                  mod_info->addr + mod_info->size))
          {
            console::fatal ("Modules intersect.");
            return;
          }
    }

  // Check CPU features.
  if (!cpu_t::check_feats ())
    {
      console::fatal ("Bootstrap CPU does not support needed features.");
      return;
    }

  // Initialize firmware.
  if (!firmware::init (elbp_info))
    {
      console::fatal ("Firmware initialization failure.");
      return;
    }

  // Initialize physical/real pages database.
  if (!pdb::init (elbp_info))
    {
      console::fatal ("Cannot initialize PDB.");
      return;
    }

  // Save first module info physical address.
  u64_t mod_info_paddr = (u64_t) elbp_info->mod_info;

  // Initialize memory management.
  if (!mm::init ())
    {
      console::fatal ("Cannot initialize memory management.");
      return;
    }

  // Initialize multiprocessing.
  if (!cpu_t::init ())
    {
      console::fatal ("Cannot initialize multiprocessing.");
      return;
    }

  u64_t main_space_entry;

  // Create main address space.
  do
    {
      if (mod_info_paddr == 0)
        {
          console::error ("No modules provided.");
          break;
        }

      space_t *main_space = space_t::create ();

      if (!main_space)
        {
          console::error ("Cannot allocate main address space.");
          return;
        }

      init::main_space.set (main_space);

      // Grant all free pages to main address space.
      pdb::grant ();

      // Make main space memory accessible.
      mm::install (main_space);

      elbp::mod_info_t mod_info;

      if (!mm::cp (mod_info_paddr, &mod_info, sizeof (mod_info)))
        {
          console::error ("Not enough memory.");
          init::main_space.null ();
          break;
        }

      u64_t cmdline_len = mm::length ((u64_t) mod_info.cmdline);

      elf::hdr_t elf_hdr;

      if (mod_info.size < sizeof (elf_hdr))
        {
          console::error ("Main module is not loadable.");
          init::main_space.null ();
          break;
        }

      if (!mm::cp (mod_info.addr, &elf_hdr, sizeof (elf_hdr)))
        {
          console::error ("Not enough memory.");
          init::main_space.null ();
          break;
        }

      if (!elf_hdr.elf_p ()
          || elf_hdr.elf_version != elf::hdr_t::VERSION_CURRENT
          || elf_hdr.version != elf::hdr_t::VERSION_CURRENT
          || elf_hdr.clazz != elf::hdr_t::CLASS_64
          || elf_hdr.data != space_t::ELF_DATA
          || elf_hdr.os_abi != space_t::ELF_OS_ABI
          || elf_hdr.machine != space_t::ELF_MACHINE)
        {
          console::error ("Main module is not loadable.");
          init::main_space.null ();
          break;
        }

      if (elf_hdr.type != elf::hdr_t::TYPE_EXEC)
        {
          console::error ("Main module is not an executable.");
          init::main_space.null ();
          break;
        }

      if (elf_hdr.shnum == 0)
        {
          console::error ("Main module contains no sections.");
          init::main_space.null ();
          break;
        }

      if (-(u64_t) (elf_hdr.shnum * elf_hdr.shentsize) <= elf_hdr.shoff
          || elf_hdr.shoff + elf_hdr.shnum * elf_hdr.shentsize > mod_info.size)
        {
          console::error ("Main module header is corrupted.");
          init::main_space.null ();
          break;
        }

      u64_t strtab_offset;
      u64_t strtab_size;

      // Locate and check section name string table.
      {
        elf::shdr_t shdr;

        if (elf_hdr.shstrndx == elf::shdr_t::IDX_UNDEF
            || elf_hdr.shstrndx >= elf::shdr_t::IDX_LORESERVE)
          {
            console::error ("Main module does not provide sections names.");
            init::main_space.null ();
            break;
          }

        if (!mm::cp (mod_info.addr + elf_hdr.shoff
                     + elf_hdr.shstrndx * elf_hdr.shentsize,
                     &shdr, sizeof (shdr)))
          {
            console::error ("Not enough memory.");
            init::main_space.null ();
            break;
          }

        if (shdr.type != elf::shdr_t::TYPE_STRTAB)
          {
            console::error ("Main module does not provide sections names.");
            init::main_space.null ();
            break;
          }

        if (-shdr.size <= shdr.offset
            || shdr.offset + shdr.size > mod_info.size)
          {
            console::error ("Main module string table overflow.");
            init::main_space.null ();
            break;
          }

        strtab_offset = shdr.offset;
        strtab_size = shdr.size;
      }

      bool found_p = false;
      u64_t top = 0;

      // Map sections.
      for (u16_t i = 0; i < elf_hdr.shnum; ++i)
        {
          elf::shdr_t shdr;
          char name[33];
          u64_t name_len;

          if (!mm::cp (mod_info.addr + elf_hdr.shoff + i * elf_hdr.shentsize,
                       &shdr, sizeof (shdr)))
            {
              console::error ("Not enough memory.");
              init::main_space.null ();
              break;
            }

          if (shdr.type == elf::shdr_t::TYPE_NULL)
            continue;

          if (shdr.name >= strtab_size)
            {
              console::error (
                "Main module section #%u name is corrupted.", i);
              init::main_space.null ();
              break;
            }

          name_len = mm::length (mod_info.addr + strtab_offset + shdr.name);

          if (name_len > sizeof (name) - 1)
            {
              if (!mm::cp (mod_info.addr + strtab_offset + shdr.name,
                           name, sizeof (name) - 4))
                {
                  console::error ("Not enough memory.");
                  init::main_space.null ();
                  break;
                }

              name[sizeof (name) - 4] = '.';
              name[sizeof (name) - 3] = '.';
              name[sizeof (name) - 2] = '.';
              name[sizeof (name) - 1] = 0;
            }
          else
            if (!mm::cp (mod_info.addr + strtab_offset + shdr.name,
                         name, name_len + 1))
              {
                console::error ("Not enough memory.");
                init::main_space.null ();
                break;
              }

          if (!(shdr.flags & elf::shdr_t::FLAG_ALLOC))
            continue;

          if (shdr.size > 0
              && (-shdr.size <= shdr.addr
                  || (shdr.type != elf::shdr_t::TYPE_NOBITS
                      && -shdr.size <= shdr.offset)))
            {
              console::error (
                "Main module section #%u (%s) size overflow.", i, name);
              init::main_space.null ();
              break;
            }

          if (shdr.addr < space_t::START
              || shdr.addr + shdr.size > space_t::END)
            {
              console::error (
                "Main module section #%u (%s) is not allocable.", i, name);
              init::main_space.null ();
              break;
            }

          if (shdr.size == 0)
            continue;

          if (shdr.flags & elf::shdr_t::FLAG_EXECINSTR
              && elf_hdr.entry >= shdr.addr
              && elf_hdr.entry < shdr.addr + shdr.size)
            found_p = true;

          if (ALIGN (shdr.addr + shdr.size, mm::PAGE_SIZE) > top)
            top = ALIGN (shdr.addr + shdr.size, mm::PAGE_SIZE);

          if (shdr.type == elf::shdr_t::TYPE_NOBITS)
            {
              if (!main_space->map_range (shdr.addr, shdr.addr + shdr.size))
                {
                  console::error ("Not enough memory.");
                  init::main_space.null ();
                  break;
                }

              utils::zero ((void *) shdr.addr, shdr.size);
            }
          else
            {
              if (shdr.addr - CUT (shdr.addr, mm::PAGE_SIZE)
                  != shdr.offset - CUT (shdr.offset, mm::PAGE_SIZE))
                {
                  console::error (
                    "Main module section #%u (%s) has wrong align.", i, name);
                  init::main_space.null ();
                  break;
                }

              if (!main_space->map_range (
                     mod_info.addr + shdr.offset, shdr.addr,
                     shdr.addr + shdr.size))
                {
                  console::error ("Not enough memory.");
                  init::main_space.null ();
                  break;
                }
            }

          mm::tte_t::flag_t tte_flags
            = (mm::tte_t::flag_t) (mm::tte_t::FLAG_DEFAULTS
                                   & ~mm::tte_t::FLAG_WRITE);

          if (shdr.flags & elf::shdr_t::FLAG_WRITE)
            tte_flags = (mm::tte_t::flag_t) (tte_flags | mm::tte_t::FLAG_WRITE);
          if (shdr.flags & elf::shdr_t::FLAG_EXECINSTR)
            tte_flags = (mm::tte_t::flag_t) (tte_flags | mm::tte_t::FLAG_EXEC);

          main_space->map_range (shdr.addr, shdr.addr + shdr.size, tte_flags);
        }

      if (init::main_space.null_p ())
        break;

      if (!found_p)
        {
          console::error ("Main module entry point is invalid.");
          init::main_space.null ();
          break;
        }

      main_space_entry = elf_hdr.entry;

      struct mod_info_t
      {
        u64_t addr;
        u64_t size;
        char *cmdline;
      };

      if (-(ALIGN (8 + mods_count * sizeof (mod_info_t) + total_cmdlines_len,
                   mm::PAGE_SIZE)
            + total_mods_size) <= top)
        {
          console::error ("Not enough virtual space.");
          init::main_space.null ();
          break;
        }

      mod_info_t *mi = (mod_info_t *) (top + 8);
      char *cmdline = (char *) mi + mods_count * sizeof (mod_info_t);

      if (!main_space->map_range (
             top, top + 8 + mods_count * sizeof (mod_info_t)
                  + total_cmdlines_len)
          || !mm::cp ((u64_t) mod_info.cmdline, cmdline, cmdline_len + 1))
        {
          console::error ("Not enough memory.");
          init::main_space.null ();
          break;
        }

      cmdline += cmdline_len + 1;
      *(u64_t *) top = mods_count - 1;
      top += ALIGN (8 + mods_count * sizeof (mod_info_t)
                    + total_cmdlines_len, mm::PAGE_SIZE);

      // Map modules.
      for (mod_info_paddr = (u64_t) mod_info.next; mod_info_paddr != 0;
           mod_info_paddr = (u64_t) mod_info.next)
        {
          if (!mm::cp (mod_info_paddr, &mod_info, sizeof (mod_info)))
            {
              console::error ("Not enough memory.");
              init::main_space.null ();
              break;
            }

          cmdline_len = mm::length ((u64_t) mod_info.cmdline);

          if (!mm::cp ((u64_t) mod_info.cmdline, cmdline, cmdline_len + 1)
              || !main_space->map_range (
                    mod_info.addr, top, top + mod_info.size))
            {
              console::error ("Not enough memory.");
              init::main_space.null ();
              break;
            }

          mi->addr = top;
          mi->size = mod_info.size;
          mi->cmdline = cmdline;

          mi += 1;
          cmdline += cmdline_len + 1;
          top += ALIGN (mod_info.size, mm::PAGE_SIZE);
        }
    }
  while (false);

  if (init::main_space.null_p ())
    {
      console::fatal ("Cannot create main address space.");
      return;
    }

  // Reuse modules and ELBP infos pages.
  if (!pdb::reap ())
    {
      console::fatal ("Cannot reuse pages.");
      return;
    }

  cpu_t *cpu = cpu_t::create (cpu_t::get_current_id ());

  if (!cpu)
    {
      console::fatal ("Cannot create object for Bootstrap CPU.");
      return;
    }

  thread_t *thread = thread_t::create (init::main_space.get ());

  if (!thread)
    {
      console::fatal ("Cannot create thread to start main address space.");
      return;
    }

  thread->set_ip (main_space_entry);
  cpu->set_scheduler (thread);

  // Initialization is finished.
  init::_done_p = true;

  init_lock.lock ();
  init_cpu (true);
  init_lock.unlock ();
}

extern "C" void
init_ap ()
{
  init_lock.lock ();
  mm::init_cpu ();
  init_cpu (false);
  init_lock.unlock ();
}

void
init::print_ctrl_transfer ()
{
  console::print ("Transferring control to main address space.\n\n");
}

END_NAMESPACE

